<meta charset="utf-8" lang="kotlin">

**Lint Development Guidelines**

(This folder should also contain architectural information documents.)

# Setup

To develop lint, use a recent IntelliJ, check out the Android tools
repository and then open up the folder `tools/` as a Gradle project.

# Code

* All new files should be written in Kotlin.

* Code should be formatted using “cd lint && gradle formatLint”, which
  will format both the Java and Kotlin files according to the
  configured style.

* All lint checks must have tests checking both for false positives and
  false negatives.

* Make sure that lint checks have the correct platform applied (e.g.
  androidSpecific=true in the issue registration for any Android
  checks.)

# Debugging lint under Gradle

To be able to attach to and debug lint when invoked from within the
Android Gradle plugin, add this to the `gradle.properties` file in the
target project before invoking gradle with the debug flags:

```
android.experimental.runLintInProcess=true
```

# Debugging lint in the IDE

By default the IDE cancels certain operations if they take too long,
including Lint. This makes debugging tricky. To disable this behavior,
start the IDE with
```
-Didea.ProcessCanceledException=disabled
```

Alternatively, you can use the internal action
“Disable ProcessCanceledException”. Note, to use internal actions
you must first go to `Help > Edit Custom Properties` and add
```
idea.is.internal=true
```

# Updating Baselines

If you've added a new lint check which has a number of existing
violations in the Studio tree, you can run the following target
to update the baselines:

```
bazel run --test_env=UPDATE_LINT_BASELINE=1  \
  $(bazel query --output=label 'kind(.*lint_test, //tools/...)')
```

# Registering Inspections

When lint runs in the IDE, all lint checks are wrapped as IntelliJ
inspections, such that they show up in the Inspections options list
etc. Lazily discovered lint checks (3rd party lint checks) will show up
as soon as lint has run to discover them, but for built-in checks we
should register them in advance such that they always show up. To do
this, open the `tools/adt/idea` project and run the
`LintInspectionRegistrationTest` test. To have your sources updated in
place, either set `$ADT_SOURCE_TREE` or edit the test first to point
the `sourceTree` var to point to your checkout.

# Deprecating and Removing APIs

If we have to remove methods (such as `UastContext`, which is slated
for removal from UAST in the next version of IntelliJ), just
deprecating doesn't seem to be enough; a lot of existing lint checks
will use APIs if the code continues to compile and run. We are guilty
of this ourselves; lint itself is still referencing a lot of deprecated
APIs.

However, if we just delete APIs, that will break existing lint checks
in the field. Therefore, to deprecate APIs, we should be using Kotlin's
special support for marking deprecated methods hidden (the
`DeprecationLevel` attribute in a `Deprecated` annotation). This will
keep the method in the runtime jar file, and older lint checks will be
able to invoke it -- but it's removed from compilation, therefore
forcing lint checks to update before we actually remove the API for
good. (This support is one of the reasons why we want all new APIs in
lint to be implemented in Kotlin.)

# Issue Granularity

We regularly get requests for a new lint check to highlight or
discourage one specific method. Here's some discussion from the most
recent time this came up, which might provide some insights into how we
think about this:

Generally, if an issue is “just” data, we shouldn't give it its own
issue id. Each issue id is configured separately (in lint.xml files),
shows up independently in the inspections UI in Studio, and it's a
global namespace.

Most of the API-specific lint checks are doing something more subtle;
for example, the ToastDetector makes sure that if you call
`makeText()`, you end up calling `show` on the resulting object, even
if indirectly. Similarly, `AlarmDetector` is specific to the
`setRepeating()` method on `AlarmDetector`, but it does data analysis
to figure out if the parameter passed as the duration is not long
enough. (There might be a few examples where we do have a lint check
dedicated to a specific API but if we do, we regret those...)

For simple discouraged APIs, we have several approaches:

1. (Best) Create a new annotation, for example @Discouraged, which we
   put in the support library and we then annotate all the platform
   calls we want to discourage users from. We then have a detector
   which looks for this annotation and flags any uses of it. There are
   many detectors like this in lint already, enforcing a wide range of
   annotations. The discourage-message could be part of the annotation
   metadata.

2. Like (1), but instead of annotations, with bundled data. For
   example, lint's API check works this way; it ships with a database
   of API “since” info that it uses for enforcement. There are a bunch
   of lint checks like this -- see `ApiLookup`, `PrivateApiLookup`,
   `PluralsDatabase`, etc.

3. Like (2), but the data is bundled directly as code/lists/arrays
   within the check: Create a shared detector which has a hardcoded
   list of APIs that we're enforcing. For example, for lint checks that
   run against Studio's own codebase, we have this detector which has a
   denylist of certain APIS we don't want used:

   https://source.corp.google.com/android/tools/base/lint/studio-checks/src/main/java/com/android/tools/lint/checks/studio/ForbiddenStudioCallDetector.kt

   In lint itself, there is a similar check, dedicated to deprecations:
   https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.kt;l=1?q=DeprecationDetector.kt

   The DeprecationDetector is used to flag APIs that are discouraged.
   It was initially written to let us deprecate things in XML files
   (since java and Kotlin have their own existing facility for
   providing warning messages for discouraged APIs). However, since
   then we've actually added Java/Kotlin specific calls, where we
   really want to not just say “deprecated” but to customize the
   specific error message -- see the Firebase/JobScheduling messages.
   And note that as of Arctic Fox you can actually vary the severity on
   a per incident basis too, so you can pick warning or error as
   default based on the specific check.

# Informational Checks

Lint checks should pinpoint a problem. They are not intended to only be
educational or to just “bring awareness” around recent changes to an API.
Especially if the lint check cannot check whether the guidance applies, for
example because it depends on policy approval which is not available for
inspection in the code.

A check which unconditionally flags code in order to educate users is often
considered by users to be “spammy”. We've had some checks like that in the
past, and they were not popular, and the issue id soon ended up in default
ignore rules for lint. For an example, see [this StackOverflow
thread](https://stackoverflow.com/questions/34173545/missing-support-for-fireb
ase-app-indexing-android-lint).

<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
